<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>GTK+ 中的事件（events）和信号（signals）</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="GUI, GTK+ events and signals, GTK+ library, C">
<meta nam="description" content="GTK+ events and signals">
<meta name="language" content="zh, en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">

<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>GTK+ 中的事件（events）和信号（signals）</h1>

<p>
在我们教程的这一个章节中，我们会谈一谈GTK+函数工具库中的，“事件”系统 。 
</p>

<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div>

<p>
GTK+函数工具库是基于<b class="keyword"> “事件”</b>系统的。所有的 GUI 应用程序无一例外都是基于“事件”驱动的。假如没有“事件”发生，则应用程序就什么都不会做。在GTK+中一个事件就是从X窗口服务器传出来的一个消息。当一个“事件”发生时，他就会通过发送一个“信号”来表示他已经做出了反应。利用GTK+还可以为“信号”绑定专门的回调函数。也就是说回调函数只对他特定的“信号”才有反应并执行。 
</p>

<pre class="code">
#include &lt;gtk/gtk.h&gt;

void button_clicked(GtkWidget *widget, gpointer data)
{
  g_print("clicked\n");
}

int main( int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;

  gtk_init(&amp;argc, &amp;argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkButton");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Click");
  gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50);
  gtk_widget_set_size_request(button, 80, 35);

  g_signal_connect(G_OBJECT(button), "clicked", 
      G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}
</pre>

<p>
在我们上面的这个示例应用程序中，我们有两个“信号”。一个是信号<b class="keyword">clicked</b>,也就是单击；另外则是信号 <b class="keyword">destroy</b>。 

</p>

<pre class="explanation">
 g_signal_connect(G_OBJECT(button), "clicked", 
     G_CALLBACK(button_clicked), NULL);
</pre>

<p>
我们用函数 <b class="keyword">g_signal_connect()</b> 去“连接”信号“clicked”和回调函<b class="keyword">button_clicked()</b>  。 

</p>


<pre class="explanation">
 void button_clicked(GtkWidget *widget, gpointer data)
 {
   g_print("clicked\n");
 }
</pre>

<p>
这个回调函数执行的功能是向终端输出“clicked”字符串。这个函数的第一行参就是那个发射信号的对象。在我们的这个例子中实参便是构件“Click button”。第二个行参是可以选择有无的。我们可以利用这个参数向回调函数传递特定的数据。在我们的例子中，并没有传递任何的参数。所以我们就在g_signal_connect()中调用回调函数的时候在调用的第二个实参中填上了“NULL”。 
</p>

<pre class="explanation">
 g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);
</pre>

<p>
如果我们单击窗口右上角的“X”或者按Atl + F4, 一个<b class="keyword">destroy</b>就被发射出去了。然后呢，我们为这个信号所绑定的回调函数<b class="keyword">gtk_main_quit()</b> 将执行，他的功能是终止整个应用程序。 

</p>


<h2>移动窗口（Moving window）</h2>

<p>
在下一个例子中，我们将展示是如何对“移动窗口”这个事件做出反应的。
</p>

<pre class="code">
#include &lt;gtk/gtk.h&gt;

void frame_callback(GtkWindow *window, 
      GdkEvent *event, gpointer data)
{
   int x, y;
   char buf[10];
   x = event->configure.x;
   y = event->configure.y;
   sprintf(buf, "%d, %d", x, y);
   gtk_window_set_title(window, buf);
}


int main(int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init(&amp;argc, &amp;argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Simple");
  gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  g_signal_connect(G_OBJECT(window), "configure-event",
        G_CALLBACK(frame_callback), NULL);

  gtk_widget_show(window);
  gtk_main();

  return 0;
}
</pre>

<p>
在这个例子中，我们随时追踪显示了位于左上角的标题栏的位置。 
</p>

<pre class="explanation">
 gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);
</pre>

<p>
上面这行代码说明了，哪一个构件将回接受到事件，并对事件的发生做出反应。一些事件使用时要已与特定的构件组装好了，另外的一些事件不得不要用一个函数 <b class="keyword">gtk_widget_add_events()</b> 去武装他。把事件类型<b class="keyword">GDK_CONFIGURE</b>填加到这个函数中。事件类型<b class="keyword">GDK_CONFIGURE</b> 包含了所有的大小、位置和用于存储事件次序的栈结构。
</p>

<pre class="explanation">
 g_signal_connect(G_OBJECT(window), "configure-event",
     G_CALLBACK(frame_callback), NULL);
</pre>

<p>
从上面可以看出，信号“ <b class="keyword">configure-event</b>“被发射了 ，则所绑定构件的大小、位置与次序栈都被捕获了。 
</p>


<pre class="explanation">
 void frame_callback(GtkWindow *window, 
     GdkEvent *event, gpointer data)
 {
   int x, y;
   char buf[10];
   x = event->configure.x;
   y = event->configure.y;
   sprintf(buf, "%d, %d", x, y);
   gtk_window_set_title(window, buf);
 }
</pre>

<p>
这个回调函数有三个行参。分别是：反射信号的构件， <b class="keyword">GdkEvent</b>和可选择的行参.。我们获取了位置坐标（x,y），并把他放在了标题栏上。 
</p>


<br>
<img src="../images/moveevent.png" alt="Move event">
<div class="figure">Figure: Move event</div>
<br>



<h2>鼠标进入信号（The enter signal）</h2>

<p>
在接下来的章节中，我们将展示如何对“<b>鼠标的进入</b>”信号作出反应。当我们的鼠标移动到我们所绑定的那个构件上时就会发出“鼠标进入信号”。 
</p>

<pre class="code">
#include &lt;gtk/gtk.h&gt;


void enter_button(GtkWidget *widget, gpointer data) 
{ 
  GdkColor color;
  color.red = 27000;
  color.green = 30325;
  color.blue = 34181;
  gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &amp;color);
}


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;

  gtk_init(&amp;argc, &amp;argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "enter signal");

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Button");
  gtk_widget_set_size_request(button, 80, 35);
  gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50);

  g_signal_connect(G_OBJECT(button), "enter", 
      G_CALLBACK(enter_button), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}
</pre>

<p>
上面的程序想实现这样的一个功能：当我们的鼠标掠过按钮构件的时候,我们的程序代码可以改变那个按钮的背景颜色。 
</p>

<pre class="explanation">
 g_signal_connect(G_OBJECT(button), "enter", 
    G_CALLBACK(enter_button), NULL);
</pre>

<p>
当信号“<b class="keyword">enter</b>”发生时，我们会调用函数 <b class="keyword">enter_button()</b> 

</p>

<pre class="explanation">
 GdkColor color;
 color.red = 27000;
 color.green = 30325;
 color.blue = 34181;
 gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &amp;color);
</pre>

<p>
在对应的回调函数中，我们通过调用函数<b class="keyword">gtk_widget_modify_bg()</b>来改变按钮的颜色。
</p>

<h2>回调函数解除绑定（Disconnecting a callback）</h2>

<p>
既然可以为一个信号绑定一个回调函数，我们当然也可以解除一个绑定。在接下来的代码示范示例中就是这样的一个例子。 
</p>

<pre class="code">
#include &lt;gtk/gtk.h&gt;


int handler_id;

void button_clicked(GtkWidget *widget, gpointer data) 
{ 
  g_print("clicked\n");
}

void toogle_signal(GtkWidget *widget, gpointer window)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
     handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
           G_CALLBACK(button_clicked), NULL);
  } else {
     g_signal_handler_disconnect(window, handler_id);
  }
}


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;
  GtkWidget *check;


  gtk_init(&amp;argc, &amp;argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Disconnect");

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Click");
  gtk_widget_set_size_request(button, 80, 30);
  gtk_fixed_put(GTK_FIXED(fixed), button, 30, 50);

  check = gtk_check_button_new_with_label("Connect");
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
  gtk_fixed_put(GTK_FIXED(fixed), check, 130, 50);

  handler_id = g_signal_connect(G_OBJECT(button), "clicked", 
        G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(check), "clicked",
        G_CALLBACK(toogle_signal), (gpointer) button);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}
</pre>

<p>
在例子中，我们生成了一个按钮和一个选择框。那个选择框的功能便是绑定或者解除绑定一个回调函数与信号“clicked”之间的关系。 
</p>

<pre class="explanation">
 handler_id = g_signal_connect(G_OBJECT(button), "clicked", 
     G_CALLBACK(button_clicked), NULL);
</pre>

<p>
<b class="keyword">g_signal_connect()</b>函数执行后会返回一个“id”数据。这就是实现了对回调函数的唯一标示。 
</p>

<pre class="explanation">
 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
    handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
          G_CALLBACK(button_clicked), NULL);
 } else {
    g_signal_handler_disconnect(window, handler_id);
 }
</pre>

<p>
这段代码决定了选择框的状态，如果选中了，就绑定否则就解除绑定。 
</p>

<br>
<img src="../images/disconnect.png" alt="Disconnect">
<div class="figure">Figure: Disconnect</div>
<br>

<h2>（拖与放的示例）Drag and Drop example</h2>

<p>
在接下一个示例中，我们将展示一个有趣的特性。我们将生成一个没有边框的窗口，然后我们将说明如何才能拖动和放置这样的一个窗口。
</p>

<pre class="code">
#include &lt;gtk/gtk.h&gt;

gboolean on_button_press (GtkWidget* widget,
  GdkEventButton * event, GdkWindowEdge edge)
{
  if (event->type == GDK_BUTTON_PRESS)
  {
    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
	  event->x_root,
	  event->y_root,
	  event->time);
    }
  }

  return FALSE;
}


int main( int argc, char *argv[])
{

  GtkWidget *window;

  gtk_init(&amp;argc, &amp;argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Drag &amp; drop");
  gtk_window_set_decorated(GTK_WINDOW (window), FALSE);
  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  g_signal_connect(G_OBJECT(window), "button-press-event",
      G_CALLBACK(on_button_press), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show(window);

  gtk_main();

  return 0;
}
</pre>

<p>
上面的这个示例程序向我们展示了如何才能生成一个可以拖拉和放置的无边框的窗口程序。
</p>

<pre class="explanation">
 gtk_window_set_decorated(GTK_WINDOW (window), FALSE);
</pre>

<p>
我们去除了窗口中的一些修饰性的部分。也就是说我们的这个窗口没有边框和标题栏。 
</p>

<pre class="explanation">
  g_signal_connect(G_OBJECT(window), "button-press-event",
      G_CALLBACK(on_button_press), NULL);
</pre>

<p>
我们把信号<b class="keyword">button-press-event</b>绑定在窗口中。 
</p>

<pre class="explanation">
gboolean on_button_press (GtkWidget* widget,
  GdkEventButton * event, GdkWindowEdge edge)
{
  if (event->type == GDK_BUTTON_PRESS)
  {
    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
	  event->x_root,
	  event->y_root,
	  event->time);
    }
  }

  return FALSE;
}
</pre>

在回调函数<b class="keyword">on_button_press()</b>中，我们放置了拖动和放置的代码。 我们检测是否鼠标被单击（左击）。然后条件判断执行函数 <b class="keyword">gtk_window_begin_move_drag()</b> 进行拖放操作。 
</p>



<h2>（一个定时器） A timer example</h2>

<p>
在接下来的示例中我们将向你展示如何去生成一个定时器。定时器通常应用于当我们要做一些重复工作的场合。譬如一个时钟，一个倒记时和增加动态的视觉效应。 
</p>

<pre class="code">
#include &lt;cairo.h&gt;
#include &lt;gtk/gtk.h&gt;
#include &lt;time.h&gt;


static char buffer[256];


static gboolean
on_expose_event(GtkWidget *widget,
    GdkEventExpose *event,
    gpointer data)
{
  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  cairo_move_to(cr, 30, 30);
  cairo_show_text(cr, buffer);

  cairo_destroy(cr);

  return FALSE;
}

static gboolean
time_handler(GtkWidget *widget)
{
  if (widget->window == NULL) return FALSE;

  time_t curtime;
  struct tm *loctime;

  curtime = time(NULL);
  loctime = localtime(&amp;curtime);
  strftime(buffer, 256, "%T", loctime);

  gtk_widget_queue_draw(widget);
  return TRUE;
}

int
main (int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *darea;

  gtk_init(&amp;argc, &amp;argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  darea = gtk_drawing_area_new();
  gtk_container_add(GTK_CONTAINER (window), darea);

  g_signal_connect(darea, "expose-event",
      G_CALLBACK(on_expose_event), NULL);
  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 170, 100);

  gtk_window_set_title(GTK_WINDOW(window), "timer");
  g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);
  gtk_widget_show_all(window);
  time_handler(window);

  gtk_main();

  return 0;
}
</pre>

<p>
我们将要在窗口中，“画”一个显示当前时间的动态效果。所用的工具，就是在我们一开始曾经提到过的Cairo函数工具库。 
</p>

<pre class="explanation">
 g_signal_connect(darea, "expose-event",
     G_CALLBACK(on_expose_event), NULL);
</pre>

<p>
首先我们要把在回调函数 <b class="keyword">on_expose_event()</b>中画出时间的数值。这个回调函数与信号进行了绑定<b class="keyword">expose-event</b>  。如果这个信号发射出去了，这个窗口就会按照我们程序所安排的一样——“立即刷新”。 
</p>

<pre class="explanation">
 g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);
</pre>

<p>
上面的这个函数注册了一个GTK+系统中的一个定时器（是抽象的不可见的）。函数 <b class="keyword">time_handler()</b> 就会被按照我们在函数中的设置在设定的时间内不断的运行。在例子中，我们设定了这个时间为1秒，一旦这个函数 <b class="keyword">g_timeout_add()</b>返回FALSE，定时器函数 <b class="keyword">time_handler()</b>就会运行。 
</p>

<pre class="explanation">
 time_handler(window);
</pre>

<p>
这行代码的作用是立即启动定时器，否则的话，你会看到1秒的延迟。 
</p>

<pre class="explanation">
 cairo_t *cr;

 cr = gdk_cairo_create(widget->window);

 cairo_move_to(cr, 30, 30);
 cairo_show_text(cr, buffer);

 cairo_destroy(cr);
</pre>

<p>
以上便是用来在当前窗口上画出当前时间的代码。如果你想了解更多的关于Cairo函数库的一些情况请访问 <a href="http://www.zetcode.com/tutorials/cairographicstutorial">版权属于ZetCode。. 
</a>.
</p>

<pre class="explanation">
 if (widget->window == NULL) return FALSE;
</pre>

<p>
在我们刷新窗口前，很显然要先注销窗口，这样的话，定时函数便被自动调用了；可当我们关闭窗口时，定时器也会被调用，这是我们不想看到了，所以需要加上以上的代码，阻止在已经关闭的构件上运行定时器。 
</p>

<pre class="explanation">
 time_t curtime;
 struct tm *loctime;

 curtime = time(NULL);
 loctime = localtime(&amp;curtime);
 strftime(buffer, 256, "%T", loctime);
</pre>

<p>
上面的代码获取了系统当前的时间。  
</p>

<pre class="explanation">
 gtk_widget_queue_draw(widget);
</pre>

<p>
这段代码会注销窗口，然后信号 <b class="keyword">expose-event</b>  就会被激活发射出去。从而被绑定的回调函数就会执行。

</p>

<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div>

<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡ 
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified April 1, 2008  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
<span class="copyright">Translation &copy; 2008 chinalmy</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>

